package com.greendizer.api.xmli;

import java.util.Date;
import java.util.List;


/**
 * Represents an invoice XMLi.
 */
public class Invoice extends AbstractElement<Group> {

  private Recipient buyer;
  private Shipping shipping;
  private String name;
  private String description;
  private String customId;
  private CurrencyEnum currency;
  private Date date;
  private Date dueDate;
  private InvoiceStatusEnum status;
  private String terms;

  /**
   * Creates an invoice with its parent XMLiBuilder.
   */
  public Invoice() {
    buyer = new Recipient("buyer");
    shipping = new Shipping();
  }

  /**
   * Adds the given group to the current invoice.
   * @param group The group to be added.
   */
  public void addGroup(Group group) {
    addChild(group);
  }

  /**
   * Removes the given group from the current invoice.
   * @param group The group to be removed.
   */
  public void removeGroup(Group group) {
    removeChild(group);
  }

  /**
   * Returns the groups contained in the current invoice.
   * @return The groups contained in the current invoice.
   */
  public List<Group> getGroups() {
    return getChildren();
  }

  /**
   * Returns buyer email address.
   * @return Buyer email address.
   */
  public String getBuyerEmail() {
    return buyer.email;
  }

  /**
   * Sets the buyer email to the given value.
   * @param email Buyer email value to be set.
   */
  public void setBuyerEmail(String email) {
    buyer.email = email;
  }

  /**
   * Returns buyer name.
   * @return Buyer name.
   */
  public String getBuyerName() {
    return buyer.name;
  }

  /**
   * Sets the buyer name to the given value.
   * @param name Buyer name value to be set.
   */
  public void setBuyerName(String name) {
    buyer.name = name;
  }

  /**
   * Return buyer address.
   * @return Buyer address.
   */
  public Address getBuyerAddress() {
    return buyer.address;
  }

  /**
   * Return invoice shipping info.
   * @return Invoice shipping info.
   */
  public Shipping getShipping() {
    return shipping;
  }

  /**
   * Returns the invoice name.
   * @return The invoice name.
   */
  public String getName() {
    return name;
  }

  /**
   * Sets the invoice name to the given value.
   * @param name Invoice name to be set.
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Returns the invoice description to the given value.
   * @return The invoice description to the given value.
   */
  public String getDescription() {
    return description;
  }

  /**
   * Sets the invoice description to the given value.
   * @param description Invoice description to be set.
   */
  public void setDescription(String description) {
    this.description = description;
  }

  /**
   * Returns the invoice custom id.
   * @return The invoice custom id.
   */
  public String getCustomId() {
    return customId;
  }

  /**
   * Set the invoice custom id to the given value.
   * @param customId Invoice custom id value to be set.
   */
  public void setCustomId(String customId) {
    this.customId = customId;
  }

  /**
   * Returns the invoice currency.
   * @return The invoice currency.
   */
  public CurrencyEnum getCurrency() {
    return currency;
  }

  /**
   * Sets the invoice currency to the given value.
   * @param currency Invoice currency value to be set.
   */
  public void setCurrency(CurrencyEnum currency) {
    this.currency = currency;
  }

  /**
   * Returns the invoice date.
   * @return The invoice date.
   */
  public Date getDate() {
    return date;
  }

  /**
   * Set the invoice date to the given value.
   * @param date Invoice date value to be set.
   */
  public void setDate(Date date) {
    this.date = date;
  }

  /**
   * Returns the invoice due date.
   * @return The invoice due date.
   */
  public Date getDueDate() {
    return dueDate;
  }

  /**
   * Sets the invoice due date to the given value.
   * @param dueDate Invoice due date value to be set.
   */
  public void setDueDate(Date dueDate) {
    this.dueDate = dueDate;
  }

  /**
   * Returns the invoice status.
   * @return The invoice status.
   */
  public InvoiceStatusEnum getStatus() {
    return status;
  }

  /**
   * Sets the invoice status to the given value.
   * @param status Invoice status value to be set.
   */
  public void setStatus(InvoiceStatusEnum status) {
    this.status = status;
  }

  /**
   * Returns the invoice terms.
   * @return The invoice terms.
   */
  public String getTerms() {
    return terms;
  }

  /**
   * Sets the invoice terms to the given value.
   * @param terms Terms value to be set.
   */
  public void setTerms(String terms) {
    this.terms = terms;
  }

  /**
   * Returns the XMLi representation of the invoice.
   */
  public String toString() {
    StringBuffer buffer = new StringBuffer();
    buffer.append(buyer.toString());
    buffer.append(shipping.toString());
    buffer.append(String.format("<name><![CDATA[%s]]></name>", name == null ? "" : name));
    buffer.append(String.format("<description><![CDATA[]]>%s</description>", description == null ? "" : description));
    buffer.append(String.format("<customId><![CDATA[%s]]></customId>", customId == null ? "" : customId));
    buffer.append(String.format("<date>%s</date>", date == null ? "" : formatDate(date)));
    buffer.append(String.format("<dueDate>%s</dueDate>", dueDate == null ? "" : formatDate(dueDate)));
    buffer.append(String.format("<total>%.5f</total>", getTotal()));
    buffer.append(String.format("<currency>%s</currency>", currency == null ? "" : currency.name()));
    buffer.append(String.format("<status>%s</status>", status == null ? "" : status.name().toLowerCase()));
    buffer.append(String.format("<terms><![CDATA[%s]]></terms>", terms == null ? "" : terms));
    buffer.append("<body");
    for (XMLNamespace namespace : getNamespaces()) {
      buffer.append(String.format(" %s", namespace.toString()));
    }
    buffer.append("><groups>");
    for (Group group : getChildren()) {
      buffer.append("<group>");
      buffer.append(group.toString());
      buffer.append("</group>");
    }
    buffer.append("</groups>");
    buffer.append(super.toString());
    buffer.append("</body>");
    return buffer.toString();
  }

  /**
   * Represents an invoice recipient.
   */
  private class Recipient {

    private final String tagName;
    private String email;
    private String name;
    private final Address address;

    /**
     * Creates a new recipient with its XML tag name.
     */
    private Recipient(String tagName) {
      this.tagName = tagName;
      this.address = new Address();
    }

    /**
     * Returns the XMLi representation of the recipient.
     */
    public String toString() {
      StringBuffer buffer = new StringBuffer();
      buffer.append(String.format("<%s>", tagName));
      buffer.append(String.format("<email><![CDATA[%s]]></email>", email == null ? "" : email));
      buffer.append(String.format("<name><![CDATA[%s]]></name>", name == null ? "" : name));
      buffer.append(address.toString());
      buffer.append(String.format("</%s>", tagName));
      return buffer.toString();
    }
  }

  /**
   * Represents an invoice address.
   */
  public class Address {

    private transient boolean empty;
    private String streetAddress;
    private String other;
    private String zipcode;
    private String city;
    private String state;
    private String country;

    /**
     * Creates a new address.
     */
    public Address() {
      empty = true;
    }

    /**
     * Tells whether the address is empty or not.
     * @return True is the address is empty, false otherwise.
     */
    public boolean isEmpty() {
      return empty;
    }

    /**
     * Returns the street address.
     * @return The street address.
     */
    public String getStreetAddress() {
      return streetAddress;
    }

    /**
     * Sets the street address to the given value.
     * @param streetAddress Street address value to be set.
     */
    public void setStreetAddress(String streetAddress) {
      this.streetAddress = streetAddress;
      this.empty = false;
    }

    /**
     * Returns the address other information.
     * @return The address other information.
     */
    public String getOther() {
      return other;
    }

    /**
     * Sets the other address information to the given value.
     * @param other Other address information value to be set.
     */
    public void setOther(String other) {
      this.other = other;
      this.empty = false;
    }

    /**
     * Returns the zipcode.
     * @return The zipcode.
     */
    public String getZipcode() {
      return zipcode;
    }

    /**
     * Sets the zipcode to the given value.
     * @param zipcode Zipcode value to be set.
     */
    public void setZipcode(String zipcode) {
      this.zipcode = zipcode;
      this.empty = false;
    }

    /**
     * Returns the address city.
     * @return The address city.
     */
    public String getCity() {
      return city;
    }

    /**
     * Sets the city to the given value.
     * @param city City value to be set.
     */
    public void setCity(String city) {
      this.city = city;
      this.empty = false;
    }

    /**
     * Returns the state.
     * @return The state.
     */
    public String getState() {
      return state;
    }

    /**
     * Sets the state to the given value.
     * @param state State value to be set.
     */
    public void setState(String state) {
      this.state = state;
      this.empty = false;
    }

    /**
     * Returns the country.
     * @return The country.
     */
    public String getCountry() {
      return country;
    }

    /**
     * Sets the country to the given value.
     * @param country Country to be set.
     */
    public void setCountry(String country) {
      this.country = country;
      this.empty = false;
    }

    /**
     * Returns the XMLi representation of the address.
     */
    public String toString() {
      if (isEmpty()) {
        return "";
      }
      StringBuffer buffer = new StringBuffer();
      buffer.append("<address>");
      buffer.append(String.format("<streetAddress><![CDATA[%s]]></streetAddress>", streetAddress == null ? "" : streetAddress));
      buffer.append(String.format("<other><![CDATA[%s]]></other>", other == null ? "" : other));
      buffer.append(String.format("<zipcode><![CDATA[%s]]></zipcode>", zipcode == null ? "" : zipcode));
      buffer.append(String.format("<city><![CDATA[%s]]></city>", city == null ? "" : city));
      buffer.append(String.format("<state><![CDATA[%s]]></state>", state == null ? "" : state));
      buffer.append(String.format("<country>%s</country>", country == null ? "" : country));
      buffer.append("</address>");
      return buffer.toString();
    }
  }

  /**
   * Represents an invoice shipping info.
   */
  public class Shipping {

    private transient boolean empty;
    private final Recipient recipient;

    /**
     * Creates a new shipping info.
     */
    public Shipping() {
      empty = true;
      recipient = new Recipient("recipient");
    }

    /**
     * Tells whether the shipping info is empty or not.
     * @return True is the shipping info is empty, false otherwise.
     */
    public boolean isEmpty() {
      return empty && recipient.address.isEmpty();
    }

    /**
     * Returns shipping recipient email address.
     * @return Shipping recipient email address.
     */
    public String getRecipientEmail() {
      return recipient.email;
    }

    /**
     * Sets the shipping recipient email to the given value.
     * @param email Shipping recipient email value to be set.
     */
    public void setRecipientEmail(String email) {
      recipient.email = email;
      empty = false;
    }

    /**
     * Returns shipping recipient name.
     * @return Shipping recipient name.
     */
    public String getRecipientName() {
      return recipient.name;
    }

    /**
     * Sets the shipping recipient name to the given value.
     * @param name Shipping recipient name value to be set.
     */
    public void setRecipientName(String name) {
      recipient.name = name;
      empty = false;
    }

    /**
     * Return shipping recipient address.
     * @return Shipping recipient address.
     */
    public Address getRecipientAddress() {
      return recipient.address;
    }

    /**
     * Returns the XMLi representation of the shipping info.
     */
    public String toString() {
      if (isEmpty()) {
        return "";
      }
      StringBuffer buffer = new StringBuffer();
      buffer.append("<shipping>");
      buffer.append(recipient.toString());
      buffer.append("</shipping>");
      return buffer.toString();
    }
  }
}
